package jxtend.processor.eclipse.visitor.handler.impl;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import jxtend.processor.reflect.ReflectField;

import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;

@SuppressWarnings("restriction")
public final class CompilationResultUtils {

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private static final ReflectField<CompilationResult, Map<IProblem, ReferenceContext>> problemsMapOfCompilationResult =
			(ReflectField) ReflectField.getField(CompilationResult.class, Map.class, "problemsMap");

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private static final ReflectField<CompilationResult, Set<IProblem>> firstErrorsOfCompilationResult =
			(ReflectField) ReflectField.getField(CompilationResult.class, Set.class, "firstErrors");

	public static void removeProblemsFromCompilationResult(
			CompilationResult compilationResult, boolean breakOnFirstOccurrence, ProblemMatcher problemMatcher) {
		assert compilationResult != null && problemMatcher != null;

		Map<IProblem, ReferenceContext> problemsMap = problemsMapOfCompilationResult.get(compilationResult);

		if ((problemsMap == null) || problemsMap.isEmpty()) { return; }

		Map<ReferenceContext, Integer> referenceContextErrorCount = new HashMap<ReferenceContext, Integer>();
		
		for (Iterator<Map.Entry<IProblem, ReferenceContext>> iterator = problemsMap.entrySet().iterator();
				iterator.hasNext();) {
			Map.Entry<IProblem, ReferenceContext> entry = iterator.next();
			IProblem problem = entry.getKey();

			Integer count = referenceContextErrorCount.get(entry.getValue());
			if (count == null) {
				count = 0;
			}

			if (problem.isError() && !problemMatcher.matchProblem(problem)) {
				count++;
			}

			referenceContextErrorCount.put(entry.getValue(), count);

			if (problemMatcher.matchProblem(problem)) {
				iterator.remove();

				removeFromProblemsArray(compilationResult, problem);

				firstErrorsOfCompilationResult.get(compilationResult).remove(problem);

				if (breakOnFirstOccurrence) {
					break;
				}
			}
		}

		for (Map.Entry<ReferenceContext, Integer> errorCountEntry : referenceContextErrorCount.entrySet()) {
			ReferenceContext referenceContext = errorCountEntry.getKey();
			int count = errorCountEntry.getValue();

			if (count == 0) {
				@SuppressWarnings("unchecked") ReflectField<ReferenceContext, Boolean> ignoreFurtherInvestigationField =
					(ReflectField<ReferenceContext, Boolean>) ReflectField.getPublicField(
							referenceContext.getClass(), Boolean.TYPE, "ignoreFurtherInvestigation");
				ignoreFurtherInvestigationField.set(referenceContext, false);
			}
		}
	}

	private static void removeFromProblemsArray(CompilationResult compilationResult, IProblem problem) {
		CategorizedProblem[] problems = compilationResult.problems;
		
		if (problems == null) { return; }

		int oldProblemsCount = compilationResult.problemCount;
		
		int problemIndex = -1;
		for (int i = 0; i < oldProblemsCount; i++) {
			if (problems[i] == problem) {
				problemIndex = i;
			}
		}

		if (problemIndex != -1) {
			for (int i = problemIndex + 1; i < problems.length; i++) {
				problems[i  - 1] = problems[i];
			}

			compilationResult.problemCount--;
		}
	}

	public abstract static class ProblemMatcher {
		private final int problemID;
		private final int argumentsCount;

		public ProblemMatcher(int problemID, int argumentsCount) {
			assert (problemID != 0) && (argumentsCount > 0);

			this.problemID = problemID;
			this.argumentsCount = argumentsCount;
		}

		public boolean matchProblem(IProblem problem) {
			return (problem.getID() == problemID)
				&& (problem.getArguments() != null) && (problem.getArguments().length == argumentsCount)
				&& doMatchProblem(problem);
		}

		protected abstract boolean doMatchProblem(IProblem problem);
	}

	private CompilationResultUtils() {
		// Utility class
	}
}
